var viz = function (graphics) {
    return {
        control : function (slides, frameRate) {
            var interval;
            return {
                next : function () {
                    slides.nextSlide();
                },

                previous : function () {
                    slides.previousSlide();
                },

                play : function () {
                    if (!interval) {
                        slides.init(graphics);
                        interval = window.setInterval(function () {
                            slides.project(graphics);
                            slides.nextFrame();
                        }, Math.floor(1000 / frameRate));
                    }
                },

                stop : function () {
                    if (interval) {
                        window.clearTimeout(interval);
                        interval = null;
                    }
                }
            };
        }
    };
};

viz.screen = function () {
    var assetIds = [];
    var frameAssets = {};
    var coordinates = viz.coordinates();

    var instance = {};
    instance.addFrameAsset = function (slideIndex, frameAsset) {
        if (!frameAssets[frameAsset.id]) {
            frameAssets[frameAsset.id] = [];
            assetIds.push(frameAsset.id);
        }
        frameAssets[frameAsset.id][slideIndex] = frameAsset;
    };

    instance.placeFrameAsset = function (slideIndex, frameAsset) {
        instance.addFrameAsset(slideIndex, frameAsset);
        return coordinates.addFrameAsset(frameAsset);
    };

    instance.drawFrameAssets = function (slideIndex, graphics) {
        for (var i = 0, assetSize = assetIds.length ; i < assetSize ; i++) {
            for (var j = slideIndex ; j >= 0 ; j--) {
                if (frameAssets[assetIds[i]][j]) {
                    var frameAsset = frameAssets[assetIds[i]][j];
                    var coordinate = coordinates.getCoordinateOf(frameAsset);
                    frameAsset.draw(graphics, coordinate && coordinate.localise({
                        widthOf : function (previousFrameAsset) {
                            return instance.widthOf(previousFrameAsset.id, graphics);
                        }
                    }, graphics));
                    break;
                }
            }
        }
    };

    instance.width = function (graphics) {
        var width = 0;
        for (var i = 0, assetSize = assetIds.length ; i < assetSize ; i++) {
            width += instance.widthOf(assetIds[i], graphics);
        }
        return width;
    };

    instance.height = function (graphics) {
        function heightOf(frameAssetId, graphics) {
            var height = 0;
            if (frameAssets[frameAssetId]) {
                for (var i = 0, slideSize = frameAssets[frameAssetId].length ; i < slideSize ; i++) {
                    if (frameAssets[frameAssetId][i]) {
                        var potentialHeight = frameAssets[frameAssetId][i].height(graphics);
                        if (potentialHeight > height) {
                            height = potentialHeight;
                        }
                    }
                }
            }
            return height;
        }

        var height = 0;
        for (var i = 0, assetSize = assetIds.length ; i < assetSize ; i++) {
            var potentialHeight = heightOf(assetIds[i], graphics);
            if (potentialHeight > height) {
                height = potentialHeight;
            }
        }
        return height;
    };

    instance.widthOf = function (frameAssetId, graphics) {
        var width = 0;
        if (frameAssets[frameAssetId]) {
            for (var i = 0, slideSize = frameAssets[frameAssetId].length ; i < slideSize ; i++) {
                if (frameAssets[frameAssetId][i]) {
                    var potentialWidth = frameAssets[frameAssetId][i].width(graphics);
                    if (potentialWidth > width) {
                        width = potentialWidth;
                    }
                }
            }
        }
        return width;
    };
    return instance;
};

viz.coordinates = function () {
    var assetIds = [];
    var coordinates = {};

    function relativeCoordinate() {
        var previousFrameAsset;
        var localised;

        return {
            placedAfter : function (frameAsset) {
                previousFrameAsset = frameAsset;
            },

            localise : function (screen) {
                if (!localised) {
                    if (previousFrameAsset) {
                        var baseCoordinate = coordinates[previousFrameAsset.id].localise(screen);
                        localised = localCoordinate(baseCoordinate.x() + screen.widthOf(previousFrameAsset), baseCoordinate.y());
                    } else {
                        localised = localCoordinate(0, 0);
                    }
                }
                return localised;
            }
        };
    }

    function localCoordinate(originX, originY) {
        return {
            x : function (localX) {
                return originX + (localX ? localX : 0);
            },
            y : function (localY) {
                return originY + (localY ? localY : 0);
            }
        };
    }

    return {
        addFrameAsset : function (frameAsset) {
            if (!coordinates[frameAsset.id]) {
                assetIds.push(frameAsset.id);
                coordinates[frameAsset.id] = relativeCoordinate();
            }
            return coordinates[frameAsset.id];
        },

        getCoordinateOf : function (frameAsset) {
            return coordinates[frameAsset.id];
        }
    };
};

viz.slides = function (screen, events) {
    var currentSlide = 0;
    var slides = [];
    for (var i = 0, eventSize = events.length ; i < eventSize ; i++) {
        var slideFactory = viz;
        var subPackages = events[i].type.split(".");
        for (var j = 0, packageSize = subPackages.length ; j < packageSize ; j++) {
            slideFactory = slideFactory[subPackages[j]];
        }

        slides.push(slideFactory(
        {
            addFrameAsset : function (frameAsset) {
                screen.addFrameAsset(i, frameAsset);
            },

            placeFrameAsset : function (frameAsset) {
                var coordinate = screen.placeFrameAsset(i, frameAsset);
                return {
                    after : function (previousFrameAsset) {
                        coordinate.placedAfter(previousFrameAsset);
                    }
                };
            }
        }, events[i]));
    }

    return {
        init : function (graphics) {
            graphics.size(screen.width(graphics), screen.height(graphics));
        },

        project : function (graphics) {
            if (currentSlide < slides.length) {
                screen.drawFrameAssets(currentSlide, graphics);
            }
        },

        nextFrame : function () {
            if (!slides[currentSlide].isLastFrame()) {
                slides[currentSlide].nextFrame();
            }
        },

        nextSlide : function () {
            if (currentSlide + 1 < slides.length) {
                for (var i = 0 ; i < currentSlide + 1 ; i++) {
                    slides[i].lastFrame();
                }
                slides[currentSlide + 1].firstFrame();
                currentSlide++;
            }
        },

        previousSlide : function () {
            if (currentSlide - 1 >= 0) {
                for (var i = 0 ; i < currentSlide - 1 ; i++) {
                    slides[i].lastFrame();
                }
                slides[currentSlide - 1].firstFrame();
                currentSlide--;
            }
        }
    };
};

viz.graphics = {};